<?php
/**
 * MySQL操作类
 * 本类是针对mysql方法的面向对象再封装
 * mysql相关函数本身存在SQL注入的潜在危险
 * mysql相关函数自5.6开始官方已废弃，在系统环境允许下建议改用mysqli或pdo
 * 在PHP5.3.0以下环境使用时，请复制本文件并删除复制文件的命名空间以使正常使用。
 * @author Fize Chan
 * @version V2.0.0.20170614
 */

namespace App\DesignPattern\Adapter;

class DriverMysql{
	const QUERY_RETURN_QUERYID = -1;
	const QUERY_RETURN_THIS = 0;
	const QUERY_RETURN_ARRAY = 1;
	const QUERY_RETURN_ASSOC = 2;
	const QUERY_RETURN_FIELD = 3;
	const QUERY_RETURN_LENGTHS = 4;
	const QUERY_RETURN_OBJECT = 5;
	const QUERY_RETURN_ROW = 6;
	
	const INFO_CLIENT = 1;
	const INFO_HOST = 2;
	const INFO_PROTO = 3;
	const INFO_SERVER = 4;
	const INFO_MYSQL = 5;
	
	/**
	 * 当前数据库服务器连接
	 * @var resource 
	 */
	private $_connect;

	/**
	 * 当前选择数据库
	 * @var string 
	 */
	private $_database;
	
	/**
	 *当前查询ID
	 * @var resource 
	 */
    protected $queryID = null;
	
	/**
	 * 返回或者影响记录数
	 * @var int
	 */
    protected $numRows = 0;
	
	/**
	 * 返回字段数
	 * @var int 
	 */
    protected $numFields = 0;
	
	/**
	 * 最后插入ID
	 * @var int 
	 */
	protected $lastInsID = null;
	
	/**
	 * 构造
	 * @param string $server 服务器名及端口
	 * @param string $user 用户名
	 * @param string $pwd 密码
	 * @param string $database 选择数据库
	 * @param string $charset 指定编码
	 * @param bool $pconnect 是否长连接
	 * @param mixed $newlink 指定link_id，由于产生新连接
	 * @param int $clientflag 指定MySQL客户端常量 
	 */
	public function __construct($server, $user, $pwd, $database = null, $charset = 'utf8', $pconnect = false, $newlink = false, $clientflag = 0){
		if($pconnect){
			$this->_connect = mysql_pconnect($server, $user, $pwd, $clientflag);
		}else{
			$this->_connect = mysql_connect($server, $user, $pwd, $newlink, $clientflag);
		}
		if(!$this->_connect){
			/**
			 * @todo 统一抛错处理
			 */
			die('Could not connect: ' . mysql_error());
		}
		$this->setCharset($charset);
		if(!is_null($database)){
			$this->selectDB($database);
		}
	}
	
	/**
	 * 析构
	 */
	public function __destruct(){
		$this->close();
	}
	
	/**
	 * 关闭当前数据库连接
	 */
	public function close(){
		if($this->_connect && get_resource_type($this->_connect) == "mysql link"){
			mysql_close($this->_connect);
		}
	}
	
	/**
	 * 选择数据库
	 * @param string $database 数据库名称
	 * @return bool
	 */
	public function selectDB($database){
		$rst = mysql_select_db($database, $this->_connect);
		if($rst){
			$this->_database = $database;
		}else{
			/**
			 * @todo 统一抛错处理
			 */
			die('Could not select database: ' . mysql_error());
		}
		return $rst;
	}
	
	/**
	 * 返回当前连接的字符集名称
	 * @return string
	 */
	public function clientEncoding(){
		return mysql_client_encoding($this->_connect);
	}
	
	/**
	 * 新建一个 MySQL 数据库
	 * 不提倡使用 mysql_create_db() 函数。
	 * @param string $name 数据库名
	 * @return bool
	 */
	public function createDB($name){
		return mysql_create_db($name, $this->_connect);
	}
	
	/**
	 * 丢弃（删除）一个 MySQL 数据库
	 * 不提倡使用 mysql_drop_db() 函数。最好用 mysql_query() 提交一条 SQL DROP DATABASE 语句来替代。
	 * @param string $name 数据库名
	 * @return bool
	 */
	public function dropDB($name){
		return mysql_drop_db($name, $this->_connect);
	}
	
	/**
	 * 返回上一个 MySQL 操作中的错误信息的数字编码
	 * @return int
	 */
	public function errno(){
		return mysql_errno($this->_connect);
	}
	
	/**
	 * 返回上一个 MySQL 操作产生的文本错误信息
	 * @return string
	 */
	public function error(){
		return mysql_error($this->_connect);
	}
	
	/**
	 * 遍历当前结果集
	 * @param callable $func 遍历函数
	 * @param int $type 以何种形式遍历,默认为关联数组形式
	 */
	public function fetch(callable $func, $type = 2){
		switch($type){
			case self::QUERY_RETURN_ARRAY :
				//以关联数组+数字数组遍历当前结果集的每一行
				while ($array = mysql_fetch_array($this->queryID, MYSQL_BOTH )){
					$func($array);
				}
				break;
			case self::QUERY_RETURN_ASSOC :
				//以关联数组遍历当前结果集的每一行
				while ($assoc = mysql_fetch_assoc($this->queryID)){
					$func($assoc);
				}
				break;
			case self::QUERY_RETURN_FIELD :
				//遍历结果集中取得列信息对象
				while ($field = mysql_fetch_field($this->queryID)){
					$func($field);
				}
				break;
			case self::QUERY_RETURN_LENGTHS :
				//遍历结果集中每个记录的每个字段输出长度
				while ($row = mysql_fetch_row($this->queryID)){
					$func(mysql_fetch_lengths($this->queryID));
				}
				break;
			case self::QUERY_RETURN_OBJECT :
				//以对象形式遍历当前结果集的每一行
				while ($object = mysql_fetch_object($this->queryID)){
					$func($object);
				}
				break;
			case self::QUERY_RETURN_ROW :
				//以数字数组遍历当前结果集的每一行
				while ($row = mysql_fetch_row($this->queryID)){
					$func($row);
				}
				break;
		}
	}
	
	/**
	 * 取得当前记录集，需要注意的是mysql方法返回的元素都为字符串类型(除了NULL)
	 * @param string $type 以何种形式表示每行记录
	 * @return array
	 */
	public function getAll($type = 2){
		$result = array();
		$this->fetch(function($one) use (&$result){
			$result[] = $one;
		}, $type);
		return $result;
	}

	/**
	 * 执行一次查询，并返回结果
	 * @param string $sql SQL查询语句
	 * @param int $type 结果返回类型
	 * @param int $seek 移动内部结果的指针 
	 * @param string $database 指定数据库，注意查询后不会切换回先前连接到的数据库
	 * @param bool $unbuffered 是否不获取和缓存结果的行
	 * @return mixed
	 */
	public function query($sql, $type = 2, $seek = 0, $database = null, $unbuffered = false){
		if(is_null($database)){
			if($unbuffered){
				$this->queryID = mysql_unbuffered_query($sql, $this->_connect);
			}else{
				$this->queryID = mysql_query($sql, $this->_connect);
			}
		}else{
			$this->queryID = mysql_db_query($database, $sql, $this->_connect);
			$this->_database = $database;
		}
		if($this->queryID === false){
			return false;
		}
		$this->numFields = mysql_num_fields($this->queryID); //取得结果集中字段的数目 
		$this->numRows = mysql_num_rows($this->queryID); //取得结果集中行的数目
		if($seek !== 0){
			mysql_data_seek($this->queryID, $seek);
		}
		if($type === -1){
			return $this->queryID;
		}elseif($type === 0){
			return $this;
		}else{
			return $this->getAll($type);
		}
	}

	/**
	 * 执行SQL语句，正确返回影响的记录行数，错误返回false
	 * @param string $sql SQL语句
	 * @return integer
	 */
	public function execute($sql){
		$result = mysql_query($sql, $this->_connect) ;
        if ($result === false) {
            return false;
        } else {
            $this->lastInsID = mysql_insert_id($this->_connect);
			$this->numRows = mysql_affected_rows($this->_connect);
            return $this->numRows;
        }
	}

	/**
	 * 列出 MySQL 服务器中所有的可用数据库
	 * @return array
	 */
	public function listDbs(){
		$db_list = mysql_list_dbs($this->_connect);
		$i = 0;
		$cnt = mysql_num_rows($db_list);
		$result = array();
		while($i < $cnt){
			$result[] = mysql_db_name($db_list, $i);
			$i ++;
		}
		return $result;
	}
	
	/**
	 * 列出指定表中的所有列信息
	 * @param string $table 表名
	 * @param string $database 数据库名，不指定则为当前数据库
	 * @return array
	 */
	public function listFields($table, $database = null){
		if(is_null($database)){
			$database = $this->_database;
		}
		$fields = mysql_list_fields($database, $table, $this->_connect);
		$columns = mysql_num_fields($fields);
		$result = array();
		for($i = 0; $i < $columns; $i ++){
			$result[] = array(
				'name' => mysql_field_name($fields, $i),
				'flags' => mysql_field_flags($fields, $i),
				'len' => mysql_field_len($fields, $i),
				'seek' => mysql_field_seek($fields, $i),
				'table' => mysql_field_table($fields, $i),
				'type' => mysql_field_type($fields, $i)
			);
		}
		return $result;
	}
	
	/**
	 * 列出 MySQL 进程
	 * @return array
	 */
	public function listProcesses(){
		$this->queryID = mysql_list_processes($this->_connect);
		return $this->getAll(self::QUERY_RETURN_ASSOC);
	}
	
	/**
	 * 列出表名
	 * @param string $databse 数据库名，不指定则为当前数据库
	 * @return array
	 */
	public function listTables($databse = null){
		if(is_null($databse)){
			$databse = $this->_database;
		}
		$result = mysql_list_tables($databse);
		$table = array();
		for($i=0; $i<mysql_num_rows($result); $i++){
			$table[] = mysql_tablename($result, $i);
		}
		return $table;
	}

	/**
	 * 释放当前结果内存 
	 * @return boolean
	 */
	public function freeResult(){
		if($this->queryID){
			return mysql_free_result($this->queryID);
		}else{
			return false;
		}
	}

	/**
     * SQL指令安全过滤
     * @access public
     * @param string $str  SQL字符串
     * @return string
     */
    private function escapeString($str) {
        if($this->_connect) {
            return mysql_real_escape_string($str,$this->_connect);
        }else{
            return mysql_escape_string($str);
        }
    }
	
	/**
	 * 获取当前连接的MYSQL相关信息
	 * @param int $type 指定信息类型
	 * @return string
	 */
	public function getInfo($type){
		switch($type){
			case self::INFO_CLIENT :
				$result = mysql_get_client_info();
				break;
			case self::INFO_HOST :
				$result = mysql_get_host_info($this->_connect);
				break;
			case self::INFO_PROTO :
				$result = mysql_get_proto_info($this->_connect);
				break;
			case self::INFO_SERVER :
				$result = mysql_get_server_info($this->_connect);
				break;
			case self::INFO_MYSQL :
				$result = mysql_info($this->_connect);
				break;
		}
		return isset($result) ? $result : '';
	}
	
	/**
	 * Ping当前服务器连接，如果没有连接则重新连接 
	 * @return boolean
	 */
	public function ping(){
		if($this->_connect){
			return mysql_ping($this->_connect);
		}else{
			return false;
		}
	}
	
	/**
	 * 返回 MySQL结果集中一个单元的内容
	 * 不推荐使用此方法
	 * @param int $row 指定行
	 * @param mixed $field 指定列
	 * @return mixed
	 */
	public function result($row, $field = 0){
		return mysql_result($this->queryID, $row, $field);
	}
	
	/**
	 * 设置当前连接的默认字符集。 
	 * @param string $charset 字符集名称
	 * @return bool
	 */
	public function setCharset($charset){
		return mysql_set_charset($charset, $this->_connect);
	}
	
	/**
	 * 返回当前服务器状态
	 * 不推荐使用此方法，建议使用 SQL 命令 SHOW STATUS来替换
	 * @return string
	 */
	public function stat(){
		return mysql_stat($this->_connect);
	}
	
	/**
	 * 返回当前mysql线程的ID
	 * @return int
	 */
	public function threadID(){
		return mysql_thread_id($this->_connect);
	}
	
	/**
     * 对要加入SQL中的值进行格式化
     * @param mixed $value 要格式化的值
     * @return string
     */
    public function parseValue($value){
        if(is_string($value)) {
            $value =  "'".$this->escapeString($value)."'";
        }elseif(is_bool($value)){
            $value =  $value ? '1' : '0';
        }elseif(is_null($value)){
            $value =  'null';
        }
        return $value;
    }
	
	/**
     * 获取最近插入的ID
     * @access public
     * @return string
     */
    public function getLastInsID() {
        return $this->lastInsID;
    }
	
	/**
	 * 开始事务
	 */
	public function startTrans(){
		$this->query("SET AUTOCOMMIT=0");
		$this->query("BEGIN");
	}
	
	/**
	 * 执行事务
	 */
	public function commit(){
		$this->query("COMMIT");
	}
	
	/**
	 * 回滚事务
	 */
	public function rollback(){
		$this->query("ROLLBACK");
	}
}